/*
 * SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
 * SPDX-FileContributor: 2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include "access.h"
#include "dfu.h"
#include "btc_ble_mesh_dfu_model.h"
#include "blob.h"
#include "mesh/slist.h"
#include "mesh/mutex.h"
#include "mesh_v1.1/dfu/dfu.h"
#include "mesh_v1.1/dfu/dfu_cli.h"

#if CONFIG_BLE_MESH_DFU_CLI
#define TARGETS_FOR_EACH(cli, target)                                      \
    SYS_SLIST_FOR_EACH_CONTAINER(                                          \
        (sys_slist_t *)&((cli)->blob.inputs)->targets, target, blob.n)

#define MSG_CTX(cli, dst)                                              \
    {                                                                  \
        .app_idx = (cli)->blob.inputs->app_idx, .addr = dst,           \
        .send_ttl = (cli)->blob.inputs->ttl,                           \
    }

#define DFU_CLI(blob_cli) CONTAINER_OF(blob_cli, struct bt_mesh_dfu_cli, blob)

_Static_assert((DFU_UPDATE_START_MSG_MAXLEN + BLE_MESH_MODEL_OP_LEN(BLE_MESH_DFU_OP_UPDATE_START) +
                BLE_MESH_MIC_SHORT) <= BLE_MESH_TX_SDU_MAX,
               "The Firmware Update Start message does not fit into the maximum outgoing SDU size.");

_Static_assert((DFU_UPDATE_INFO_STATUS_MSG_MINLEN +
                BLE_MESH_MODEL_OP_LEN(BLE_MESH_DFU_OP_UPDATE_INFO_STATUS) + BLE_MESH_MIC_SHORT)
               <= BLE_MESH_RX_SDU_MAX,
               "The Firmware Update Info Status message does not fit into the maximum incoming SDU "
               "size.");

enum req {
    REQ_NONE,
    REQ_METADATA,
    REQ_IMG,
    REQ_STATUS,
    INTERNAL_REQ_IMG,
};

enum {
    FLAG_FAILED = BIT(0),
    FLAG_CANCELLED = BIT(1),
    FLAG_SKIP_CAPS_GET = BIT(2),
    FLAG_RESUME = BIT(3),
    FLAG_COMPLETED = BIT(4),
};

static void bt_mesh_dfu_cli_rm_req_from_list(bt_mesh_dfu_cli_req_t *req);
static int req_free(bt_mesh_dfu_cli_req_t *req);

static int32_t dfu_cli_timeout = (10 * MSEC_PER_SEC);

static struct {
    bt_mesh_mutex_t op_lock;
    sys_slist_t list;
} dfu_req_list;

static struct bt_mesh_dfu_target *target_get(struct bt_mesh_dfu_cli *cli,
                                             uint16_t addr)
{
    struct bt_mesh_dfu_target *target;

    TARGETS_FOR_EACH(cli, target) {
        if (addr == target->blob.addr) {
            return target;
        }
    }

    return NULL;
}

static void req_timeout_handler(struct k_work *work)
{
    struct k_delayed_work *timer = NULL;
    bt_mesh_dfu_cli_req_t *req = NULL;
    struct bt_mesh_msg_ctx ctx = {0};

    BT_WARN("Receive dfu status message timeout");

    bt_mesh_r_mutex_lock(&dfu_req_list.op_lock);

    timer = CONTAINER_OF(work, struct k_delayed_work, work);

    if (timer && !k_delayed_work_free(timer)) {
        req = CONTAINER_OF(work, bt_mesh_dfu_cli_req_t, timer.work);
        memcpy(&ctx, &req->ctx, sizeof(struct bt_mesh_msg_ctx));
        if (req->type == REQ_NONE) {
            assert(0);
        }
        if (req == NULL) {
            assert(0);
        }

        bt_mesh_dfu_client_cb_evt_to_btc(req->opcode, BTC_BLE_MESH_EVT_DFU_CLIENT_TIMEOUT,
                                         req->dfu_cli->mod, &ctx, NULL, 0);
    }

    bt_mesh_dfu_cli_rm_req_from_list(req);
    req_free(req);

    bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);
}

static bt_mesh_dfu_cli_req_t* req_alloc(void)
{
    bt_mesh_dfu_cli_req_t *req = bt_mesh_calloc(sizeof(bt_mesh_dfu_cli_req_t));
    if (!req) {
        BT_ERR("device firmware client allocl failed");
        return NULL;
    }

    req->ctx.addr = BLE_MESH_ADDR_UNASSIGNED;
    req->type = REQ_NONE;
    k_delayed_work_init(&req->timer, req_timeout_handler);
    return req;
}

static int req_free(bt_mesh_dfu_cli_req_t *req)
{
    if (req->node.next) {
        BT_ERR("req still in list");
        return -EINVAL;
    }

    k_delayed_work_free(&req->timer);
    bt_mesh_free(req);
    return 0;
}

static int is_valid_req(bt_mesh_dfu_cli_req_t *req)
{
    if (!req ||
            req->ctx.addr == BLE_MESH_ADDR_UNASSIGNED) {
        return false;
    }

    return true;
}

static void bt_mesh_dfu_cli_req_list_init(void)
{
    sys_slist_init(&dfu_req_list.list);
    bt_mesh_r_mutex_create(&dfu_req_list.op_lock);
}

static void bt_mesh_dfu_cli_add_req_to_list(bt_mesh_dfu_cli_req_t *req)
{
    if (!is_valid_req(req)) {
        return;
    }

    bt_mesh_r_mutex_lock(&dfu_req_list.op_lock);

    sys_slist_append(&dfu_req_list.list, &req->node);

    bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);
}

static void bt_mesh_dfu_cli_rm_req_from_list(bt_mesh_dfu_cli_req_t *req)
{
    if (!is_valid_req(req)) {
        return;
    }

    bt_mesh_r_mutex_lock(&dfu_req_list.op_lock);

    sys_slist_find_and_remove(&dfu_req_list.list, &req->node);

    bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);

    k_delayed_work_cancel(&req->timer);
}

static bt_mesh_dfu_cli_req_t* bt_mesh_dfu_req_find_by_addr(uint16_t addr)
{
    bt_mesh_dfu_cli_req_t *req = NULL;
    sys_snode_t *peek = NULL;

    if (addr == BLE_MESH_ADDR_UNASSIGNED) {
        return NULL;
    }

    bt_mesh_r_mutex_lock(&dfu_req_list.op_lock);

    SYS_SLIST_FOR_EACH_NODE(&dfu_req_list.list, peek) {
        req = (bt_mesh_dfu_cli_req_t*)peek;
        if (req->ctx.addr == addr) {
            bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);
            return req;
        }
    }

    bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);
    return NULL;
}

static void bt_mesh_dfu_req_list_free(void)
{
    bt_mesh_dfu_cli_req_t *req = NULL;
    sys_snode_t *peek = NULL;
    sys_snode_t *nxt = NULL;

    bt_mesh_r_mutex_lock(&dfu_req_list.op_lock);

    SYS_SLIST_FOR_EACH_NODE_SAFE(&dfu_req_list.list, peek, nxt) {
        req = (bt_mesh_dfu_cli_req_t*)peek;
        if (req->params) {
            bt_mesh_free(req->params);
        }
        bt_mesh_dfu_cli_rm_req_from_list(req);
        k_delayed_work_cancel(&req->timer);
        req_free(req);
    }

    bt_mesh_r_mutex_unlock(&dfu_req_list.op_lock);
    return;
}

static void target_failed(struct bt_mesh_dfu_cli *cli,
                          struct bt_mesh_dfu_target *target,
                          enum bt_mesh_dfu_status status)
{
    target->status = status;

    BT_ERR("Target 0x%04x failed: %u", target->blob.addr, status);

    /* Invalidate blob status to prevent the target from being included in
     * future sending:
     */
    if (target->blob.status == BT_MESH_BLOB_SUCCESS) {
        target->blob.status = BT_MESH_BLOB_ERR_INTERNAL;
    }

    if (cli->cb && cli->cb->lost_target) {
        cli->cb->lost_target(cli, target);
    }
}

static void dfu_complete(struct bt_mesh_dfu_cli *cli)
{
    BT_DBG("");

    if (cli->cb && cli->cb->ended) {
        cli->cb->ended(cli, BLE_MESH_DFU_SUCCESS);
    }
}

static void dfu_applied(struct bt_mesh_dfu_cli *cli)
{
    BT_DBG("");

    cli->xfer.state = STATE_APPLIED;

    if (cli->cb && cli->cb->applied) {
        cli->cb->applied(cli);
    }
}

static void dfu_failed(struct bt_mesh_dfu_cli *cli,
                       enum bt_mesh_dfu_status reason)
{
    BT_DBG("%u", reason);

    cli->xfer.flags |= FLAG_FAILED;

    if (cli->cb && cli->cb->ended) {
        cli->cb->ended(cli, reason);
    }
}

static int req_setup(struct bt_mesh_dfu_cli *cli, enum req type,
                     uint32_t opcode, struct bt_mesh_msg_ctx *ctx,
                     void *params)
{
    if (!cli->req ||
            cli->req->type != REQ_NONE) {
        return -EBUSY;
    }

    cli->req->params = params;
    cli->req->type = type;
    cli->req->dfu_cli = cli;
    cli->req->opcode = opcode;

    if (ctx) {
        memcpy(&cli->req->ctx, ctx, sizeof(struct bt_mesh_msg_ctx));
    }

    return 0;
}

static int req_wait(struct bt_mesh_dfu_cli *cli, k_timeout_t timeout)
{
    int err = ESP_OK;
    bt_mesh_dfu_cli_req_t *req = cli->req;

    req->timeout = timeout;
    k_delayed_work_submit(&req->timer, timeout);
    bt_mesh_dfu_cli_add_req_to_list(req);
    return err;
}

static bool targets_active(struct bt_mesh_dfu_cli *cli)
{
    struct bt_mesh_dfu_target *target;

    TARGETS_FOR_EACH(cli, target) {
        if (target->status == BLE_MESH_DFU_SUCCESS) {
            return true;
        }
    }

    return false;
}

/*******************************************************************************
 * Blob client
 ******************************************************************************/
static void refresh(struct bt_mesh_dfu_cli *cli);

static void blob_caps(struct bt_mesh_blob_cli *b,
                      const struct bt_mesh_blob_cli_caps *caps)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    int err;

    if (!caps) {
        dfu_failed(cli, BLE_MESH_DFU_ERR_RESOURCES);
        return;
    }

    cli->xfer.blob.block_size_log = caps->max_block_size_log;
    cli->xfer.blob.chunk_size = caps->max_chunk_size;

    /* If mode is not already set and server reported it supports all modes
     * default to PUSH, otherwise set value reported by server. If mode
     * was set and server supports all modes, keep old value; set
     * reported value otherwise.
     */
    if (!(cli->xfer.blob.mode & BT_MESH_BLOB_XFER_MODE_ALL)) {
        cli->xfer.blob.mode =
            caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ?
            BT_MESH_BLOB_XFER_MODE_PUSH : caps->modes;
    } else {
        cli->xfer.blob.mode =
            caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ?
            cli->xfer.blob.mode : caps->modes;
    }

    err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, cli->xfer.io);
    if (err) {
        BT_ERR("Starting BLOB xfer failed: %d", err);
        dfu_failed(cli, BLE_MESH_DFU_ERR_BLOB_XFER_BUSY);
    }
}

static void blob_lost_target(struct bt_mesh_blob_cli *b,
                             struct bt_mesh_blob_target *blobt,
                             enum bt_mesh_blob_status reason)
{
    struct bt_mesh_dfu_target *target =
        CONTAINER_OF(blobt, struct bt_mesh_dfu_target, blob);
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);

    if ((cli->xfer.state == STATE_CONFIRM || cli->xfer.state == STATE_APPLY) &&
            target->effect == BLE_MESH_DFU_EFFECT_UNPROV) {
        /* Reset status for such targets to use them in consequent procedures. See sections
         * 7.1.2.6 and 7.1.2.9 of the MeshDFU.
         */
        target->blob.status = BT_MESH_BLOB_SUCCESS;
        return;
    }

    target_failed(cli, target, BLE_MESH_DFU_ERR_INTERNAL);
}

static void blob_suspended(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);

    BT_DBG("BLOB transfer suspended");

    cli->xfer.state = STATE_SUSPENDED;

    if (cli->cb && cli->cb->suspended) {
        cli->cb->suspended(cli);
    }
}

static void blob_end(struct bt_mesh_blob_cli *b,
                     const struct bt_mesh_blob_xfer *xfer, bool success)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    bt_mesh_dfu_cli_req_t *req = NULL;

    if (b->tx.target) {
        req = bt_mesh_dfu_req_find_by_addr(b->tx.target->addr);
        if (req) {
            req->img_cb = NULL;
        }
    }

    if (success) {
        refresh(cli);
        return;
    }

    if (cli->xfer.state == STATE_CANCEL) {
        /* The user cancelled the transfer, DFU will end when all
         * targets have been notified.
         */
        return;
    }

    if (cli->xfer.state != STATE_TRANSFER) {
        BT_ERR("Blob failed in invalid state %u", cli->xfer.state);
        return;
    }

    dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
}

const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers = {
    .caps = blob_caps,
    .lost_target = blob_lost_target,
    .suspended = blob_suspended,
    .end = blob_end,
};

/*******************************************************************************
 * Message sending
 ******************************************************************************/

static void tx_start(uint16_t dur, int err, void *cb_data);
static void tx_end(int err, void *cb_data);

static const struct bt_mesh_send_cb send_cb = {
    .start = tx_start,
    .end = tx_end,
};

static void tx_start(uint16_t dur, int err, void *cb_data)
{
    if (err) {
        tx_end(err, cb_data);
    }
}

static void tx_end(int err, void *cb_data)
{
    struct bt_mesh_dfu_cli *cli = cb_data;

    blob_cli_broadcast_tx_complete(&cli->blob);
}

static int tx(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
              struct net_buf_simple *buf, const struct bt_mesh_send_cb *cb,
              struct bt_mesh_dfu_cli *cli)
{
    int err;

    err = bt_mesh_model_send(mod, ctx, buf, cb, cli);
    if (err) {
        BT_ERR("Send err: %d", err);
        if (cb) {
            cb->end(err, cli);
        }
        return err;
    }

    return 0;
}

static int info_get(struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx,
                    uint8_t idx, uint8_t max_count,
                    const struct bt_mesh_send_cb *cb)
{
    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_INFO_GET, 2);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_INFO_GET);
    net_buf_simple_add_u8(&buf, idx);
    net_buf_simple_add_u8(&buf, max_count);

    return tx(cli->mod, ctx, &buf, cb, cli);
}

static void send_info_get(struct bt_mesh_blob_cli *b, uint16_t dst)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst);

    cli->req->img_cnt = 0xff;

    info_get(cli, &ctx, 0, cli->req->img_cnt, &send_cb);
}

static void send_update_start(struct bt_mesh_blob_cli *b, uint16_t dst)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst);
    struct bt_mesh_dfu_target *target;

    if (b->tx.ctx.force_unicast) {
        target = target_get(cli, dst);
    } else {
        target = SYS_SLIST_PEEK_HEAD_CONTAINER(
                     (sys_slist_t *) & ((cli)->blob.inputs)->targets,
                     target, blob.n);
    }

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_START,
                              DFU_UPDATE_START_MSG_MAXLEN);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_START);

    net_buf_simple_add_u8(&buf, cli->blob.inputs->ttl);
    net_buf_simple_add_le16(&buf, cli->blob.inputs->timeout_base);
    net_buf_simple_add_le64(&buf, cli->xfer.blob.id);
    net_buf_simple_add_u8(&buf, target->img_idx);
    net_buf_simple_add_mem(&buf, cli->xfer.slot->metadata,
                           cli->xfer.slot->metadata_len);

    (void)tx(cli->mod, &ctx, &buf, &send_cb, cli);
}

static void send_update_get(struct bt_mesh_blob_cli *b, uint16_t dst)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst);

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_GET, 0);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_GET);

    (void)tx(cli->mod, &ctx, &buf, &send_cb, cli);
}

static void send_update_cancel(struct bt_mesh_blob_cli *b, uint16_t dst)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst);

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_CANCEL, 0);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_CANCEL);

    (void)tx(cli->mod, &ctx, &buf, &send_cb, cli);
}

static void send_update_apply(struct bt_mesh_blob_cli *b, uint16_t dst)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst);

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_APPLY, 0);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_APPLY);

    (void)tx(cli->mod, &ctx, &buf, &send_cb, cli);
}

/*******************************************************************************
 * Distribution procedure
 ******************************************************************************/
static void transfer(struct bt_mesh_blob_cli *b);
static void apply(struct bt_mesh_dfu_cli *cli);
static void applied(struct bt_mesh_blob_cli *b);
static void confirmed(struct bt_mesh_blob_cli *b);
static void cancelled(struct bt_mesh_blob_cli *b);

static void initiate(struct bt_mesh_dfu_cli *cli)
{
    struct blob_cli_broadcast_ctx tx = {
        .send = send_update_start,
        .next = transfer,
        .acked = true,
    };
    struct bt_mesh_dfu_target *target;
    int img_idx = -1;

    /** If firmware img index is the same for all targets, we can send Firmware Update Start
     * message using multicast address. Otherwise, it has to be send in a unicast way.
     */
    TARGETS_FOR_EACH(cli, target) {
        if (img_idx == -1) {
            img_idx = target->img_idx;
        } else if (target->img_idx != img_idx) {
            tx.force_unicast = true;
            break;
        }
    }

    BT_DBG("");

    cli->op = BLE_MESH_DFU_OP_UPDATE_STATUS;
    cli->xfer.state = STATE_TRANSFER;

    blob_cli_broadcast(&cli->blob, &tx);
}

static void skip_targets_from_broadcast(struct bt_mesh_dfu_cli *cli, bool skip)
{
    struct bt_mesh_dfu_target *target;

    TARGETS_FOR_EACH(cli, target) {
        /* If distributor is in the targets list, or target is in Verify phase,
         * disable it until Retrieve Capabilities and BLOB Transfer procedures
         * are completed.
         */
        if (bt_mesh_has_addr(target->blob.addr) ||
                target->phase == BLE_MESH_DFU_PHASE_VERIFY) {
            target->blob.skip = skip;
            break;
        }
    }
}

static bool transfer_skip(struct bt_mesh_dfu_cli *cli)
{
    struct bt_mesh_dfu_target *target;

    TARGETS_FOR_EACH(cli, target) {
        if (!bt_mesh_has_addr(target->blob.addr) || !target->blob.skip) {
            return false;
        }
    }

    return true;
}

static void transfer(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    int err;

    BT_DBG("");

    if (!targets_active(cli)) {
        dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
        return;
    }

    skip_targets_from_broadcast(cli, true);

    if (transfer_skip(cli)) {
        /* If distributor only updates itself, or all targets are in Verify phase,
         * proceed to the refresh step immediately.
         */
        refresh(cli);
        return;
    }

    if (cli->xfer.flags & FLAG_RESUME) {
        cli->xfer.flags ^= FLAG_RESUME;
        err = bt_mesh_blob_cli_resume(b);
        if (err) {
            BT_ERR("Resuming BLOB xfer failed: %d", err);
            dfu_failed(cli, BLE_MESH_DFU_ERR_BLOB_XFER_BUSY);
        }
    } else if (cli->xfer.flags & FLAG_SKIP_CAPS_GET) {
        cli->xfer.flags ^= FLAG_SKIP_CAPS_GET;
        err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, cli->xfer.io);
        if (err) {
            BT_ERR("Starting BLOB xfer failed: %d", err);
            dfu_failed(cli, BLE_MESH_DFU_ERR_BLOB_XFER_BUSY);
        }
    } else {
        err = bt_mesh_blob_cli_caps_get(&cli->blob, cli->blob.inputs);
        if (err) {
            BT_ERR("Failed starting blob xfer: %d", err);
            dfu_failed(cli, BLE_MESH_DFU_ERR_BLOB_XFER_BUSY);
        }
    }
}

static void refreshed(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);

    if (!targets_active(cli)) {
        dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
        return;
    }

    cli->xfer.state = STATE_VERIFIED;
    dfu_complete(cli);
}

static void refresh(struct bt_mesh_dfu_cli *cli)
{
    const struct blob_cli_broadcast_ctx tx = {
        .send = send_update_get,
        .next = refreshed,
        .acked = true
    };

    BT_DBG("");

    cli->xfer.state = STATE_REFRESH;
    cli->op = BLE_MESH_DFU_OP_UPDATE_STATUS;

    /* If distributor is in the targets list, enable it again so it participates in Distribute
     * Firmware procedure.
     */
    skip_targets_from_broadcast(cli, false);

    blob_cli_broadcast(&cli->blob, &tx);
}

static void apply(struct bt_mesh_dfu_cli *cli)
{
    const struct blob_cli_broadcast_ctx tx = {
        .send = send_update_apply,
        .next = applied,
        .acked = true
    };

    BT_DBG("");

    cli->xfer.state = STATE_APPLY;
    cli->op = BLE_MESH_DFU_OP_UPDATE_STATUS;

    blob_cli_broadcast(&cli->blob, &tx);
}

static void applied(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);

    if (!targets_active(cli)) {
        dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
        return;
    }

    dfu_applied(cli);
}

static enum bt_mesh_dfu_iter target_img_cb(struct bt_mesh_dfu_cli *cli,
                                           struct bt_mesh_msg_ctx *ctx,
                                           uint8_t idx, uint8_t cnt,
                                           const struct bt_mesh_dfu_img *img,
                                           void *cb_data)
{
    struct bt_mesh_dfu_target *target;

    if ((img->fwid_len != cli->xfer.slot->fwid_len) ||
            memcmp(cli->xfer.slot->fwid, img->fwid, img->fwid_len)) {
        return BLE_MESH_DFU_ITER_CONTINUE;
    }

    target = target_get(cli, ctx->addr);
    if (target) {
        BT_DBG("SUCCESS: 0x%04x applied dfu (as image %u)", ctx->addr,
               idx);
        target->phase = BLE_MESH_DFU_PHASE_APPLY_SUCCESS;
        blob_cli_broadcast_rsp(&cli->blob, &target->blob);
    } else {
        BT_WARN("Target 0x%04x not found", ctx->addr);
    }

    return BLE_MESH_DFU_ITER_STOP;
}

static void confirm(struct bt_mesh_dfu_cli *cli)
{
    const struct blob_cli_broadcast_ctx tx = {
        .send = send_info_get,
        .next = confirmed,
        .acked = true,
        .optional = true,
    };

    BT_DBG("");

    cli->op = BLE_MESH_DFU_OP_UPDATE_INFO_STATUS;
    cli->req->img_cb = target_img_cb;
    cli->req->ttl = cli->blob.inputs->ttl;

    blob_cli_broadcast(&cli->blob, &tx);
}

static void confirmed(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);
    struct bt_mesh_dfu_target *target;
    bool success = false;

    cli->req->img_cb = NULL;

    TARGETS_FOR_EACH(cli, target) {
        if (target->status != BLE_MESH_DFU_SUCCESS) {
            /* Target either failed at earlier stage or during confirmation. In any
             * case, the app is already notified. Don't consider the target here.
             */
            continue;
        }

        if (target->effect == BLE_MESH_DFU_EFFECT_UNPROV) {
            if (!target->blob.acked) {
                success = true;
                continue;
            }

            BT_DBG("Target 0x%04x still provisioned", target->blob.addr);
            target->phase = BLE_MESH_DFU_PHASE_APPLY_FAIL;
            target_failed(cli, target, BLE_MESH_DFU_ERR_INTERNAL);
        } else if (!target->blob.acked) {
            BT_DBG("Target 0x%04x failed to respond", target->blob.addr);
            target->phase = BLE_MESH_DFU_PHASE_APPLY_FAIL;
            target_failed(cli, target, BLE_MESH_DFU_ERR_INTERNAL);
        } else if (target->status == BLE_MESH_DFU_SUCCESS) {
            success = true;
        }
    }

    if (success) {
        cli->xfer.state = STATE_IDLE;
        cli->xfer.flags = FLAG_COMPLETED;

        if (cli->cb && cli->cb->confirmed) {
            cli->cb->confirmed(cli);
        }
    } else {
        dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
    }
}

static void cancel(struct bt_mesh_dfu_cli *cli)
{
    const struct blob_cli_broadcast_ctx tx = {
        .send = send_update_cancel,
        .next = cancelled,
        .acked = true
    };

    BT_DBG("");

    cli->op = BLE_MESH_DFU_OP_UPDATE_STATUS;

    blob_cli_broadcast(&cli->blob, &tx);
}

static void cancelled(struct bt_mesh_blob_cli *b)
{
    struct bt_mesh_dfu_cli *cli = DFU_CLI(b);

    cli->xfer.flags |= FLAG_CANCELLED;
    dfu_failed(cli, BLE_MESH_DFU_ERR_INTERNAL);
}

/*******************************************************************************
 * Message handlers
 ******************************************************************************/

static int handle_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
                         struct net_buf_simple *buf)
{
    struct bt_mesh_dfu_cli *cli = mod->user_data;
    bt_mesh_dfu_cli_req_t *req = NULL;
    enum bt_mesh_dfu_status status;
    enum bt_mesh_dfu_phase phase;
    struct bt_mesh_dfu_target *target;
    uint8_t byte;

    byte = net_buf_simple_pull_u8(buf);
    status = byte & BIT_MASK(3);
    phase = byte >> 5;

    req = bt_mesh_dfu_req_find_by_addr(ctx->addr);

    // The req will be NULL if the target is a group address.
    if (req && req->type == REQ_STATUS) {
        if (req->params) {
            struct bt_mesh_dfu_target_status *rsp = req->params;

            rsp->status = status;
            rsp->phase = phase;
            if (buf->len == 13) {
                rsp->ttl = net_buf_simple_pull_u8(buf);
                rsp->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5);
                rsp->timeout_base = net_buf_simple_pull_le16(buf);
                rsp->blob_id = net_buf_simple_pull_le64(buf);
                rsp->img_idx = net_buf_simple_pull_u8(buf);
            } else if (buf->len) {
                return -EINVAL;
            }

            rsp->ttl = 0U;
            rsp->effect = BLE_MESH_DFU_EFFECT_NONE;
            rsp->timeout_base = 0U;
            rsp->blob_id = 0U;
            rsp->img_idx = 0U;
        }
        bt_mesh_dfu_client_cb_evt_to_btc(req->opcode, BTC_BLE_MESH_EVT_DFU_CLIENT_RECV_GET_RSP,
                                         req->dfu_cli->mod, &req->ctx, req->params,
                                         sizeof(struct bt_mesh_dfu_metadata_status));
        bt_mesh_free(req->params);
        bt_mesh_dfu_cli_rm_req_from_list(req);
        k_delayed_work_cancel(&req->timer);
        req_free(req);
    }

    if (cli->op != BLE_MESH_DFU_OP_UPDATE_STATUS) {
        return 0;
    }

    target = target_get(cli, ctx->addr);
    if (!target) {
        BT_WARN("Unknown target 0x%04x", ctx->addr);
        return -ENOENT;
    }

    BT_DBG("%u phase: %u, cur state: %u", status, phase, cli->xfer.state);

    target->phase = phase;

    if (cli->xfer.state == STATE_APPLY && phase == BLE_MESH_DFU_PHASE_IDLE &&
            status == BLE_MESH_DFU_ERR_WRONG_PHASE) {
        BT_DBG("Response received with Idle phase");
        blob_cli_broadcast_rsp(&cli->blob, &target->blob);
        return 0;
    }

    if (status != BLE_MESH_DFU_SUCCESS) {
        target_failed(cli, target, status);
        blob_cli_broadcast_rsp(&cli->blob, &target->blob);
        return 0;
    }

    if (buf->len == 13) {
        net_buf_simple_pull_u8(buf); /* ttl */
        target->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5);
        net_buf_simple_pull_le16(buf); /* timeout */

        if (net_buf_simple_pull_le64(buf) != cli->xfer.blob.id) {
            BT_WARN("Invalid BLOB ID");
            target_failed(cli, target, BLE_MESH_DFU_ERR_BLOB_XFER_BUSY);
            blob_cli_broadcast_rsp(&cli->blob, &target->blob);
            return 0;
        }

        target->img_idx = net_buf_simple_pull_u8(buf);

        BT_DBG("Target 0x%04x receiving transfer", ctx->addr);
    } else if (buf->len) {
        return -EINVAL;
    }

    if (cli->xfer.state == STATE_REFRESH) {
        if (phase == BLE_MESH_DFU_PHASE_VERIFY) {
            BT_DBG("Still pending...");
            return 0;
        } else if (phase == BLE_MESH_DFU_PHASE_VERIFY_FAIL) {
            BT_WARN("Verification failed on target 0x%04x",
                    target->blob.addr);
            target_failed(cli, target, BLE_MESH_DFU_ERR_WRONG_PHASE);
        }
    } else if (cli->xfer.state == STATE_APPLY) {
        if (phase != BLE_MESH_DFU_PHASE_APPLYING &&
                (target->effect == BLE_MESH_DFU_EFFECT_UNPROV ||
                 phase != BLE_MESH_DFU_PHASE_IDLE)) {
            BT_WARN("Target 0x%04x in phase %u after apply",
                    target->blob.addr, phase);
            target_failed(cli, target, BLE_MESH_DFU_ERR_WRONG_PHASE);
            blob_cli_broadcast_rsp(&cli->blob, &target->blob);
            return 0;
        }
        return 0;
    } else if (cli->xfer.state == STATE_CONFIRM) {
        if (phase == BLE_MESH_DFU_PHASE_APPLYING) {
            BT_DBG("Still pending...");
            return 0;
        }

        if (phase != BLE_MESH_DFU_PHASE_IDLE) {
            BT_WARN("Target 0x%04x in phase %u after apply",
                    target->blob.addr, phase);
            target->phase = BLE_MESH_DFU_PHASE_APPLY_FAIL;
            target_failed(cli, target, BLE_MESH_DFU_ERR_WRONG_PHASE);
            blob_cli_broadcast_rsp(&cli->blob, &target->blob);
            return 0;
        }
    } else if (cli->xfer.state == STATE_CANCEL) {
        target->phase = BLE_MESH_DFU_PHASE_TRANSFER_CANCELED;
    }

    blob_cli_broadcast_rsp(&cli->blob, &target->blob);

    return 0;
}

static int handle_info_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
                              struct net_buf_simple *buf)
{
    struct bt_mesh_dfu_cli *cli = mod->user_data;
    struct bt_mesh_dfu_target *target;
    bt_mesh_dfu_cli_req_t *req = NULL;
    enum bt_mesh_dfu_iter it = BLE_MESH_DFU_ITER_CONTINUE;
    uint8_t img_cnt, idx;

    req = bt_mesh_dfu_req_find_by_addr(ctx->addr);
    if (!req) {
        return 0;
    }

    if (req->type == INTERNAL_REQ_IMG) {
        goto confirm_procedure;
    }

    if (!req->img_cb ||
            req->type != REQ_IMG) {
        BT_WARN("Unexpected info status from 0x%04x req %p type %d", ctx->addr, req, req->type);
        return 0;
    }

    img_cnt = net_buf_simple_pull_u8(buf);
    if (img_cnt < req->img_cnt) {
        req->img_cnt = img_cnt;
    }

    idx = net_buf_simple_pull_u8(buf);
    if (idx >= img_cnt) {
        BT_WARN("Invalid idx %u", idx);
        return -ENOENT;
    }

    BT_DBG("Image list from 0x%04x from index %u", ctx->addr, idx);

    while (buf->len && req->img_cb && idx < req->img_cnt) {
        char uri_buf[CONFIG_BLE_MESH_DFU_URI_MAXLEN + 1];
        struct bt_mesh_dfu_img img;
        size_t uri_len;

        img.fwid_len = net_buf_simple_pull_u8(buf);
        if (buf->len < img.fwid_len + 1) {
            BT_WARN("Invalid format: fwid");
            return -EINVAL;
        }

        img.fwid = net_buf_simple_pull_mem(buf, img.fwid_len);

        uri_len = net_buf_simple_pull_u8(buf);
        if (buf->len < uri_len) {
            BT_WARN("Invalid format: uri");
            return -EINVAL;
        }

        BT_DBG("\tImage %u\n\r\tfwid: %s", idx, bt_hex(img.fwid, img.fwid_len));

        if (uri_len) {
            size_t uri_buf_len =
                MIN(CONFIG_BLE_MESH_DFU_URI_MAXLEN, uri_len);

            memcpy(uri_buf, net_buf_simple_pull_mem(buf, uri_len),
                   uri_buf_len);
            uri_buf[uri_buf_len] = '\0';
            img.uri = uri_buf;
        } else {
            img.uri = NULL;
        }

        it = req->img_cb(cli, ctx, idx, img_cnt, &img,
                         req->params);
        if (it != BLE_MESH_DFU_ITER_CONTINUE) {
            if (req->type == REQ_IMG) {
                bt_mesh_dfu_cli_rm_req_from_list(req);
                req_free(req);
            }

            return 0;
        }

        idx++;
    }

    if (idx < req->img_cnt) {
        BT_DBG("Fetching more images (%u/%u)", idx, req->img_cnt);
        ctx->send_ttl = req->ttl;
        info_get(cli, ctx, idx, req->img_cnt - idx,
                 (req->type == REQ_IMG) ? NULL : &send_cb);
        return 0;
    } else {
        if (req->img_cb) {
            // used to notify cb function the image output finished
            req->img_cb(cli, ctx, idx, img_cnt, NULL, NULL);
        }
    }

    bt_mesh_dfu_cli_rm_req_from_list(req);
    req_free(req);
    return 0;

confirm_procedure:
    /* Confirm-procedure termination: */
    target = target_get(cli, ctx->addr);
    if (target) {
        BT_WARN("Target 0x%04x failed to apply image: %s", ctx->addr,
                bt_hex(cli->xfer.slot->fwid, cli->xfer.slot->fwid_len));
        target->phase = BLE_MESH_DFU_PHASE_APPLY_FAIL;
        target_failed(cli, target, BLE_MESH_DFU_ERR_INTERNAL);
        blob_cli_broadcast_rsp(&cli->blob, &target->blob);
    }

    bt_mesh_dfu_cli_rm_req_from_list(req);
    req_free(req);

    return 0;
}

static int handle_metadata_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
                                  struct net_buf_simple *buf)
{
    bt_mesh_dfu_cli_req_t *req = NULL;
    struct bt_mesh_dfu_metadata_status *rsp = NULL;
    uint8_t hdr, idx;

    hdr = net_buf_simple_pull_u8(buf);
    idx = net_buf_simple_pull_u8(buf);

    req = bt_mesh_dfu_req_find_by_addr(ctx->addr);
    if (!req) {
        BT_WARN("Unexcept metadata status form 0x%04x", ctx->addr);
        return 0;
    }

    rsp = req->params;

    if (req->type != REQ_METADATA ||
            idx != rsp->idx) {
        BT_WARN("Unexpected metadata status from 0x%04x img %u",
                ctx->addr, idx);
        if (req->type != REQ_METADATA) {
            BT_WARN("Expected %u", req->type);
        } else {
            BT_WARN("Expected 0x%04x img %u", req->ctx.addr, idx);
        }

        return 0;
    }

    rsp->status = hdr & BIT_MASK(3);
    rsp->effect = (hdr >> 3);

    bt_mesh_dfu_client_cb_evt_to_btc(req->opcode, BTC_BLE_MESH_EVT_DFU_CLIENT_RECV_GET_RSP,
                                     req->dfu_cli->mod, &req->ctx, req->params,
                                     sizeof(struct bt_mesh_dfu_metadata_status));

    bt_mesh_free(rsp);
    bt_mesh_dfu_cli_rm_req_from_list(req);
    req_free(req);

    return 0;
}

const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[] = {
    {BLE_MESH_DFU_OP_UPDATE_STATUS, 1, (void *)handle_status},
    {BLE_MESH_DFU_OP_UPDATE_INFO_STATUS, 2, (void *)handle_info_status},
    {BLE_MESH_DFU_OP_UPDATE_METADATA_STATUS, 2, (void *)handle_metadata_status},
    BLE_MESH_MODEL_OP_END,
};

static int dfu_cli_init(struct bt_mesh_model *mod)
{
    struct bt_mesh_dfu_cli *cli = mod->user_data;

    if (mod->elem_idx != 0) {
        BT_ERR("DFU update client must be instantiated on first elem");
        return -EINVAL;
    }

    cli->mod = mod;

    cli->blob.cb = &_bt_mesh_dfu_cli_blob_handlers;

    bt_mesh_dfu_cli_req_list_init();

    return 0;
}

static void dfu_cli_reset(struct bt_mesh_model *mod)
{
    struct bt_mesh_dfu_cli *cli = mod->user_data;

    bt_mesh_dfu_req_list_free();
    cli->req = NULL;
    cli->xfer.state = STATE_IDLE;
    cli->xfer.flags = 0;
}

static int dfu_cli_deinit(struct bt_mesh_model *mod)
{
    dfu_cli_reset(mod);
    return 0;
}

const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb = {
    .init = dfu_cli_init,
#if CONFIG_BLE_MESH_DEINIT
    .deinit = dfu_cli_deinit,
#endif
};

/*******************************************************************************
 * Public API
 ******************************************************************************/

int bt_mesh_dfu_cli_send(struct bt_mesh_dfu_cli *cli,
                         const struct bt_mesh_blob_cli_inputs *inputs,
                         const struct bt_mesh_blob_io *io,
                         const struct bt_mesh_dfu_cli_xfer *xfer)
{
    struct bt_mesh_dfu_target *target;

    if (bt_mesh_dfu_cli_is_busy(cli)) {
        return -EBUSY;
    }

    cli->xfer.blob.mode = xfer->mode;
    cli->xfer.blob.size = xfer->slot->size;

    if (xfer->blob_id == 0) {
        bt_mesh_rand(&cli->xfer.blob.id, sizeof(cli->xfer.blob.id));
    } else {
        cli->xfer.blob.id = xfer->blob_id;
    }

    cli->xfer.io = io;
    cli->blob.inputs = inputs;
    cli->xfer.slot = xfer->slot;
    cli->xfer.flags = 0U;

    if (xfer->blob_params) {
        cli->xfer.flags |= FLAG_SKIP_CAPS_GET;
        cli->xfer.blob.block_size_log = xfer->blob_params->block_size_log;
        cli->xfer.blob.chunk_size = xfer->blob_params->chunk_size;
    }

    /* Phase will be set based on target status messages: */
    TARGETS_FOR_EACH(cli, target) {
        target->status = BLE_MESH_DFU_SUCCESS;
        target->phase = BLE_MESH_DFU_PHASE_UNKNOWN;
    }

    initiate(cli);
    return 0;
}

int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli)
{
    int err;

    err = bt_mesh_blob_cli_suspend(&cli->blob);
    if (!err) {
        cli->xfer.state = STATE_SUSPENDED;
    }

    return err;
}

int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli)
{
    struct bt_mesh_dfu_target *target;

    if (cli->xfer.state != STATE_SUSPENDED) {
        return -EINVAL;
    }

    cli->xfer.flags = FLAG_RESUME;

    /* Restore timed out targets. */
    TARGETS_FOR_EACH(cli, target) {
        if (!!target->blob.timedout) {
            target->status = BLE_MESH_DFU_SUCCESS;
            target->phase = BLE_MESH_DFU_PHASE_UNKNOWN;
        }
    }

    initiate(cli);
    return 0;
}

int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli,
                           struct bt_mesh_msg_ctx *ctx)
{
    bt_mesh_dfu_cli_req_t *req = NULL;
    if (ctx) {
        int err;
        req = req_alloc();
        if (!req) {
            return -ENOMEM;
        }
        cli->req = req;
        err = req_setup(cli, REQ_STATUS, BLE_MESH_DFU_OP_UPDATE_CANCEL, ctx, NULL);
        if (err) {
            return err;
        }
        bt_mesh_dfu_cli_add_req_to_list(req);

        BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_CANCEL, 0);
        bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_CANCEL);

        err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL);
        if (err) {
            cli->req->type = REQ_NONE;
            return err;
        }

        return req_wait(cli, K_MSEC(dfu_cli_timeout));
    }

    if (cli->xfer.state == STATE_IDLE) {
        return -EALREADY;
    }

    cli->xfer.state = STATE_CANCEL;
    blob_cli_broadcast_abort(&cli->blob);
    cancel(cli);
    return 0;
}

int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli)
{
    if (cli->xfer.state != STATE_VERIFIED) {
        return -EBUSY;
    }

    apply(cli);

    return 0;
}

int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli)
{
    bt_mesh_dfu_cli_req_t *req = NULL;

    if (cli->xfer.state != STATE_APPLIED) {
        return -EBUSY;
    }

    req = req_alloc();
    if (!req) {
        BT_ERR("%s: alloc req failed", req);
        return -ENOMEM;
    }

    cli->req = req;
    req_setup(cli, INTERNAL_REQ_IMG, BLE_MESH_DFU_OP_UPDATE_INFO_GET, NULL, NULL);
    bt_mesh_dfu_cli_add_req_to_list(req);

    cli->xfer.state = STATE_CONFIRM;
    confirm(cli);

    return 0;
}

uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli)
{
    if (cli->xfer.state == STATE_TRANSFER) {
        return bt_mesh_blob_cli_xfer_progress_active_get(&cli->blob);
    }

    if (cli->xfer.state == STATE_IDLE) {
        if (cli->xfer.flags & FLAG_COMPLETED) {
            return 100U;
        }
        return 0U;
    }

    return 100U;
}

bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli)
{
    return (cli->xfer.state == STATE_TRANSFER ||
            cli->xfer.state == STATE_REFRESH ||
            cli->xfer.state == STATE_APPLY ||
            cli->xfer.state == STATE_CONFIRM) &&
           !(cli->xfer.flags & FLAG_FAILED);
}

int bt_mesh_dfu_cli_imgs_get(struct bt_mesh_dfu_cli *cli,
                             struct bt_mesh_msg_ctx *ctx,
                             bt_mesh_dfu_img_cb_t cb, void *cb_data,
                             uint8_t first_idx, uint8_t max_count)
{
    int err;

    if (bt_mesh_dfu_req_find_by_addr(ctx->addr)) {
        BT_WARN("Exists waiting request");
        return -EBUSY;
    }

    cli->req = req_alloc();
    if (!cli->req) {
        return -ENOMEM;
    }

    err = req_setup(cli, REQ_IMG, BLE_MESH_DFU_OP_UPDATE_INFO_GET, ctx, cb_data);
    if (err) {
        req_free(cli->req);
        return err;
    }

    cli->req->img_cb = cb;
    cli->req->ttl = ctx->send_ttl;
    cli->req->img_cnt = max_count;

    err = info_get(cli, ctx, first_idx, cli->req->img_cnt,  NULL);
    if (err) {
        cli->req->img_cb = NULL;
        cli->req->type = REQ_NONE;
        req_free(cli->req);
        return err;
    }

    err = req_wait(cli, K_MSEC(dfu_cli_timeout));

    return err;
}

int bt_mesh_dfu_cli_metadata_check(struct bt_mesh_dfu_cli *cli,
                                   struct bt_mesh_msg_ctx *ctx, uint8_t img_idx,
                                   struct net_buf_simple *metadata)
{
    int err;
    bt_mesh_dfu_cli_req_t *req = NULL;

    struct bt_mesh_dfu_metadata_status *rsp = bt_mesh_calloc(sizeof(struct bt_mesh_dfu_metadata_status));
    if (!rsp) {
        BT_ERR("Alloc metadata status failed");
        return -ENOMEM;
    }
    req = req_alloc();
    if (!req) {
        return -ENOMEM;
    }
    cli->req = req;
    err = req_setup(cli, REQ_METADATA, BLE_MESH_DFU_OP_UPDATE_METADATA_CHECK, ctx, rsp);
    if (err) {
        return err;
    }

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_METADATA_CHECK,
                              1 + CONFIG_BLE_MESH_DFU_METADATA_MAXLEN);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_METADATA_CHECK);

    net_buf_simple_add_u8(&buf, img_idx);

    if (metadata) {
        net_buf_simple_add_mem(&buf, metadata->data, metadata->len);
    }

    rsp->idx = img_idx;

    err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL);
    if (err) {
        cli->req->type = REQ_NONE;
        return err;
    }

    return req_wait(cli, K_MSEC(dfu_cli_timeout));
}

int bt_mesh_dfu_cli_status_get(struct bt_mesh_dfu_cli *cli,
                               struct bt_mesh_msg_ctx *ctx)
{
    int err;
    bt_mesh_dfu_cli_req_t *req = NULL;

    struct bt_mesh_dfu_target_status *rsp = bt_mesh_calloc(sizeof(struct bt_mesh_dfu_target_status));
    if (!rsp) {
        BT_ERR("dfu target status alloc failed");
        return -ENOMEM;
    }

    req = req_alloc();
    if (!req) {
        return -ENOMEM;
    }
    cli->req = req;
    err = req_setup(cli, REQ_STATUS, BLE_MESH_DFU_OP_UPDATE_GET, ctx, rsp);
    if (err) {
        return err;
    }

    BLE_MESH_MODEL_BUF_DEFINE(buf, BLE_MESH_DFU_OP_UPDATE_GET, 0);
    bt_mesh_model_msg_init(&buf, BLE_MESH_DFU_OP_UPDATE_GET);

    err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL);
    if (err) {
        cli->req->type = REQ_NONE;
        return err;
    }

    return req_wait(cli, K_MSEC(dfu_cli_timeout));
}

int32_t bt_mesh_dfu_cli_timeout_get(void)
{
    return dfu_cli_timeout;
}

void bt_mesh_dfu_cli_timeout_set(int32_t t)
{
    dfu_cli_timeout = t;
}
#endif /* CONFIG_BLE_MESH_DFU_CLI */
